#!/bin/bash

DBUS_NAME="org.cairodock.CairoDock"
DBUS_PATH="/org/cairodock/CairoDock"
DBUS_INTERFACE="org.cairodock.CairoDock"
APP_NAME="cdpavoctrl"

DBUS_STD_ARG="--session --dest=$DBUS_NAME $DBUS_PATH/$APP_NAME $DBUS_INTERFACE.applet"

declare -A SINK_INPUT
declare -a SINK_INPUT_IDX       # Индексы всех подключенных клиентов 
declare -A SINK_INPUT_PID_IDX   # Соответствие pid'ов индексам (pid'ы могу принадлежать дочерним процессам)
declare -A SINKS
declare -a SINKS_IDX            # Индексы всех приемников
declare -A CPID_ACTIVE_PID      # Связь дочерних процессов с PID'ом окна в фокусе

TIMEOUT=1                       # в секундах
VOLUME_STEP=3                   # Шаг изменения громкости, в процентах 

ACTIVE_WINDOW_PID="" 
ACTIVE_IDX=""                   # Клиент выбранный в качестве активного. Критерий выбора в "fct_get_active_index"
SINK_DEFAULT_IDX=""             # Приемник по умолчанию
SINK_INPUT_ALSA_PLAYBACK=""     # true - если есть ALSA клиенты
SINK_INPUT_UNIQ_RUNNING_IDX=""  # Индекс клиента если он единственный проигрывающий
SINK_INPUT_ACTIVE_WINDOW_IDX=""

LOCK_PROC=/tmp/cairo-dock-pa-plugin-proc-lock
LOCK_SOCKET=/tmp/cairo-dock-pa-plugin-pipe-lock
CMD_SOCKET=/tmp/cairo-dock-pa-plugin-pipe
TMP_SOCKET=/tmp/cairo-dock-pa-plugin-tmp-pipe

#######################################################################
### 
#######################################################################
fct_init(){
  local index=0 next=0 sink_input_running_cnt=0
  mkfifo "$TMP_SOCKET"

  #--------------------------------------------------------------------
  # Parse "list-sink-inputs" and "list-sinks"
  { pacmd list-sinks ; pacmd list-sink-inputs  ;} |
      perl -ne 's|(^\s+volume\:).+?(\d+)\%.*|\1 \2| ;\
                s|(^\s+sink\:\s\d+).*|\1| ;\
                s|(^>>>\s+)(\d+).+| COUNT: \2| ;\
                print if s|^\s+(\*?)\s*(\w+[\w\.\-\_\s]+?)[\s\:\=\"]+(.+?)\"?\n|\1\2\#\&\$\3\n|' > "$TMP_SOCKET" & # На всякий случай заковыристый резделитеь "#&$"

  while read str ; do

    # ----
    env="${str%\#\&\$*}"
    val="${str#*\#\&\$}"

    # ----
    [ "$env" = 'COUNT' ]  && ((++next))    

    if [ $next -eq 1 ] ; then
    #### Сначала "list-sinks"
      [ "$env" = '*index' ] && env=index  && SINK_DEFAULT_IDX=$index
      [ "$env" = 'index'  ] && index=$val && SINK_IDX[${#SINK_IDX[@]}]=$index
      SINKS["$index$env"]="$val"

    elif [ $next -eq 2 ] ; then
    #### Потом "list-sink-inputs"
      [ "$env" = 'index' ] && index=$val && SINK_INPUT_IDX[${#SINK_INPUT_IDX[@]}]=$index 
      SINK_INPUT["$index$env"]="$val" 

    fi
  done < "$TMP_SOCKET"

  #--------------------------------------------------------------------
  fct_active_window

  #--------------------------------------------------------------------
  for index in ${SINK_INPUT_IDX[@]} ; do
    # находим pa-клиента с активным окном
    [ "${CPID_ACTIVE_PID[${SINK_INPUT["${index}application.process.id"]}]}" = "$ACTIVE_WINDOW_PID" ] && SINK_INPUT_ACTIVE_WINDOW_IDX=$index

    # Если "application.icon_name" не определен - используем в качестве имени значка "application.process.binary"
    if ! [ "${SINK_INPUT["${index}application.icon_name"]}" ] ; then
      SINK_INPUT["${index}application.icon_name"]=${SINK_INPUT[${index}application.process.binary]}
    fi

    # Проверяем наличие значка и если отсутсвует - заменяем на стандартный
    if [ ! -e ~/.config/cairo-dock/current_theme/icons/${SINK_INPUT["${index}application.icon_name"]}.* ] ; then
      SINK_INPUT["${index}application.icon_name"]="applications-multimedia"
    fi

    # Переводим звук из процентов в единицы
    SINK_INPUT["${index}VOLUME_UNIT"]=$(( ${SINKS[${SINK_INPUT["${index}sink"]}volume steps]} / 100 * ${SINK_INPUT["${index}volume"]} ))
    SINK_INPUT["${index}VOLUME_STEP"]=$(( ${SINKS[${SINK_INPUT["${index}sink"]}volume steps]} / 100 * ${VOLUME_STEP} ))

    # если из всех текущих клиентов статус "RUNNING" только у одного, а остальные на паузе - запоминаем его. 
    if [ "${SINK_INPUT["${index}state"]}" = "RUNNING" ] ; then
    #----
      [ $(( sink_input_running_cnt++ )) -eq 0 ] \
        && SINK_INPUT_UNIQ_RUNNING_IDX=$index \
        || SINK_INPUT_UNIQ_RUNNING_IDX=""
    fi
    
    # Необходимо учитывать, что клиенты работающие через ALSA plug-in, всегда указывают "state" = "RUNNING", независимо от реального состояния
    if [ "${SINK_INPUT["${index}media.name"]}" = "ALSA Playback" ] ; then
      SINK_INPUT_ALSA_PLAYBACK=true
    fi
    
  done

  #--------------------------------------------------------------------
  fct_get_active_index
  fct_action_handler init
}

#######################################################################
###
#######################################################################
fct_active_window(){
  local cpid index

  #####################################
  # Получаем PID активного окна
  set - $(xprop -id $(xprop -root | perl -ne 'print /^_NET_ACTIVE_WINDOW\(WINDOW\):.+?(\d+x\w+)/') \
    | perl -ne "print /_NET_WM_PID\(CARDINAL\).+?(\d+)/")

  # Некоторые окна, например Chromium, проигрывают звук из дочерних процессов.
  # Поэтому, необходимо, для активно окна, связать дочерние PID'ы с родительским PID'ом.
  ACTIVE_WINDOW_PID=$1 ; CPID_ACTIVE_PID[$ACTIVE_WINDOW_PID]=$ACTIVE_WINDOW_PID
  
  ps o pid= --ppid $ACTIVE_WINDOW_PID > "$TMP_SOCKET" &
  while read cpid ; do
    CPID_ACTIVE_PID[$cpid]=$ACTIVE_WINDOW_PID
  done < "$TMP_SOCKET"

  #----
}

#######################################################################
### Интелектуальный выбор индекса клиента
### Intelligent selection of the client index 
#######################################################################
fct_get_active_index() {

#   echo "Уникальное: $SINK_INPUT_UNIQ_RUNNING_IDX Активное окно: $SINK_INPUT_ACTIVE_WINDOW_IDX Alsa: $SINK_INPUT_ALSA_PLAYBACK"

  if [ ${#SINK_INPUT_IDX[@]} -eq 1 ] ; then 
  #### Единственный клиент
    ACTIVE_IDX=${SINK_INPUT_IDX[0]}

  elif [ "$SINK_INPUT_UNIQ_RUNNING_IDX" -a ! "$SINK_INPUT_ALSA_PLAYBACK" ] ; then
  #### Единственный клиент со статусом "RUNNING", остальные на паузе, не зависимо от активности окна. (только если нет ALSA клиентов,которые не показывают свое состояние)
    ACTIVE_IDX=$SINK_INPUT_UNIQ_RUNNING_IDX

  elif [ "$SINK_INPUT_ACTIVE_WINDOW_IDX" ] ; then
  #### Окно клиента активно, status игнорируется
    ACTIVE_IDX=$SINK_INPUT_ACTIVE_WINDOW_IDX

#   elif [ "$SINK_INPUT_ACTIVE_WINDOW_IDX" -a "${SINK_INPUT["${SINK_INPUT_ACTIVE_WINDOW_IDX}state"]}" = "RUNNING" ] ; then
#   #### Окно клиента активно и status=runnung
#     ACTIVE_IDX="$SINK_INPUT_ACTIVE_WINDOW_IDX"

  else 
    ACTIVE_IDX=""

  fi
}

#######################################################################
###
#######################################################################
fct_volume_handler(){
  local action=$1 index=$2 
#   echo +$1 +$2
  case "$action" in 
    get_input_volume) true ;;
     get_sink_volume) true ;; 
       sink_input_up|sink_input_down ) 
                      [ "$action" = "sink_input_up" ] && sign=+ || sign=-
                      max_volume=${SINKS[${SINK_INPUT["${index}sink"]}volume steps]}
                      next_volume_unit=$(( ${SINK_INPUT["${index}VOLUME_UNIT"]} $sign ${SINK_INPUT["${index}VOLUME_STEP"]} ))
                      next_volume_percent=$(( ${next_volume_unit} / ( $max_volume / 100 ) ))

                      [ $next_volume_percent -gt 100 ] && next_volume_percent=100
                      [ $next_volume_percent -lt   0 ] && next_volume_percent=0
                      [ $next_volume_unit -gt $max_volume ] && next_volume_unit=$max_volume
                      [ $next_volume_unit -lt   0 ] && next_volume_unit=0

                      SINK_INPUT["${index}volume"]=$next_volume_percent
                      SINK_INPUT["${index}VOLUME_UNIT"]=$next_volume_unit

                      pacmd set-sink-input-volume $index $next_volume_unit &>/dev/null
                      dbus-send $DBUS_STD_ARG.SetLabel string:"${SINK_INPUT["${index}application.name"]} ${next_volume_percent}% / Master ${SINKS[${SINK_INPUT["${ACTIVE_IDX}sink"]}volume]}%"
                      ;;
            sink_up ) true ;;
          sink_down ) true ;;
          sink_mute ) true ;;
  esac
}


#######################################################################
###
#######################################################################
fct_action_handler() {
  local action=$1 index=$2

  case "$action" in
      scroll_up   ) [ "$ACTIVE_IDX" ] && fct_volume_handler sink_input_up   $ACTIVE_IDX  || fct_action_handler no_sink_input ;;
    scroll_down   ) [ "$ACTIVE_IDX" ] && fct_volume_handler sink_input_down $ACTIVE_IDX  || fct_action_handler no_sink_input ;;
             init ) if [ "$ACTIVE_IDX" ] ; then
                      #----
                      dbus-send $DBUS_STD_ARG.SetLabel string:"${SINK_INPUT[${ACTIVE_IDX}application.name]} ${SINK_INPUT[${ACTIVE_IDX}volume]}% / Master ${SINKS[${SINK_INPUT["${ACTIVE_IDX}sink"]}volume]}%"
                      dbus-send $DBUS_STD_ARG.SetIcon string:${SINK_INPUT["${ACTIVE_IDX}application.icon_name"]}
                    else
                      fct_action_handler no_sink_input
                    fi
                    ;;
    no_sink_input ) dbus-send $DBUS_STD_ARG.ShowDialog string:"$( echo -e 'You must choose the window which volume you want to change.')" int32:4 ;;
            click ) dbus-send $DBUS_STD_ARG.ShowDialog string:"Click" int32:4 ;;
     middle_click ) dbus-send $DBUS_STD_ARG.ShowDialog string:"Middle Click" int32:4 ;;
  esac
}

#######################################################################
###
#######################################################################
fct_read_signal(){
  local action

  exec 5<>${CMD_SOCKET}
  # ----
  # ожидаем сигналы... при появлении "terminate" - завершемся
  while read -t3 action <&5 ; do
    fct_action_handler $action
    [ "$action" = "terminate" ] && break
  done
}

#######################################################################
### 
#######################################################################
fct_socket(){

  exec 7>${LOCK_SOCKET}
  flock -w5 -x 7 || fct_exit_handler error "Error: Line: $LINENO; Func:${FUNCNAME[0]} )-:"

    if test -p ${CMD_SOCKET} ; then
      #Предварительно сбрасываем, на случай, если сигнал был послан уже после того как завершился цикл обработки сигналов.      
      echo reset > ${CMD_SOCKET} & 
      until grep -q reset < ${CMD_SOCKET} ; do true ; done ; 
    fi

    if [ $1 = create ] ; then
      # ----
      # создаем сокет через который будем получать сигналы, процесс посылающий сигналы должен проверять блокировку
      rm -f ${CMD_SOCKET} ;  mkfifo ${CMD_SOCKET} || fct_exit_handler error "Error: Line: $LINENO; Func:${FUNCNAME[0]} )-:"

    elif [ $1 = delete ] ; then
      rm -f ${CMD_SOCKET}
    fi

  flock -u 7
  exec 7>&-

}


#######################################################################
###
#######################################################################
fct_exit_handler(){
  # Здесь будет город-сад! There will be a garden city!
  dbus-send $DBUS_STD_ARG.SetIcon string:~/.config/cairo-dock/third-party/cdpavoctrl/icon
  dbus-send $DBUS_STD_ARG.SetLabel string:"PulseAudio"

  # ----
  fct_socket delete
  rm $TMP_SOCKET
  exit
}

#######################################################################
###
#######################################################################

# ----
# 
exec 6>"${LOCK_PROC}"
flock -w 0 -e 6 || exit 1

fct_socket create

# sleep 10 
# exit
fct_init
fct_read_signal
fct_exit_handler

